/*
 * Copyright 2005-2010 Edward L. Stauff.                  Contact: <EdStauff@gmail.com>.
 * 
 * This file, "CharEncodings.cs", is part of the "Stauffware.Common" class library.
 * "Stauffware.Common" is free software: you can redistribute it and/or 
 * modify it under the terms of the GNU Lesser Public License as published by the 
 * Free Software Foundation, either version 3 of the License, or (at your option) 
 * any later version.
 * 
 * "Stauffware.Common" is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser Public License for more 
 * details.
 * 
 * You should have received a copy of the GNU Lesser Public License along with 
 * "Stauffware.Common".  If not, see <http://www.gnu.org/licenses/>.
 */

using System;
using System.Collections.Generic;
using System.Text;

namespace Stauffware.Common.Core
{
	/// <summary>
	/// Encapsulates the three most commonly used character encodings: ASCII, UNICODE and XML entities.
	/// </summary>
	public sealed class CharEncoding
	{
		#region Public Conversion Methods

		/// <summary>
		/// Converts any entities in a given string into their Unicode equivalents.
		/// </summary>
		public static string EntitiesToUnicode(string input)
		{
			return Convert(input, E2U).Replace("&amp;", "&");
		}

		/// <summary>
		/// Converts to entities any Unicode characters in the given string that
		/// have an entity representation.
		/// </summary>
		public static string UnicodeToEntities(string input)
		{
			if (string.IsNullOrEmpty(input)) return input;
			return Convert(input.Replace("&", "&amp;"), U2E);
		}

		/// <summary>
		/// Converts (some) non-ASCII characters in the given Unicode string
		/// to reasonable ASCII equivalents.
		/// </summary>
		public static string UnicodeToAscii(string input)
		{
			return Convert(input, U2A);
		}

		/// <summary>
		/// Converts (some) entities in the given string to reasonable
		/// ASCII approximations.
		/// </summary>
		public static string EntitiesToAscii(string input)
		{
			return Convert(input, E2A).Replace("&amp;", "&");
		}

		/// <summary>
		/// Converts a string to its C# string literal representation.
		/// </summary>
		/// <remarks>
		/// The returned string is enclosed in double-quotes, with "funny"
		/// characters escaped.
		/// If the input is null, the return value is "null" (without the quotes).
		/// </remarks>
		public static string ToCsharpLiteral(string input)
		{
			if (input == null) return "null";
			string output = "\"";
			foreach (char c in input)
				output += EscapeForCsharp(c);
			return output + "\"";
		}

		/// <summary>
		/// Converts a character to its C# character literal representation.
		/// </summary>
		/// <remarks>
		/// The returned string is enclosed in single-quotes, with "funny"
		/// characters escaped.
		/// </remarks>
		public static string ToCsharpLiteral (char c)
		{
			return "'" + EscapeForCsharp(c) + "'";
		}

		private static string EscapeForCsharp (char c)
		{
			if (c == '\"')
				return "\\\"";
			else if (c == '\\')
				return "\\\\";
			else if (c == '\0')
				return "\\0";
			else if (c == '\a')
				return "\\a";
			else if (c == '\b')
				return "\\b";
			else if (c == '\f')
				return "\\f";
			else if (c == '\n')
				return "\\n";
			else if (c == '\r')
				return "\\r";
			else if (c == '\t')
				return "\\t";
			else if (c == '\v')
				return "\\v";
			else if (c < 0x20)
				return "\\x" + ((int)c).ToString("X2");
			else if (c > 0x7F)
				return "\\u" + ((int)c).ToString("X4");
			else return "" + c;
		}

		#endregion

		#region Customization Methods

		/// <summary>
		/// Adds a character mapping.
		/// </summary>
		/// <param name="entity">
        /// may be null; otherwise must begin with an ampersand and end with a
        /// semicolon
        /// </param>
		/// <param name="unicode">usually 1 character; may be null</param>
		/// <param name="ascii">usually 1 character; may be null</param>
		public static void Add(string entity, string unicode, string ascii)
		{
			if (entity != null && unicode != null)
			{
				E2U.Add(entity, unicode);
				U2E.Add(unicode, entity);
			}

			if (entity != null && ascii != null)
				E2A.Add(entity, ascii);

			if (unicode != null && ascii != null)
				U2A.Add(unicode, ascii);
		}

		/// <summary>
		/// Removes a character mapping.
		/// </summary>
		public static void RemoveUnicode(string entity, char unicode) 
		{
			U2A.Remove(""+unicode);
			U2E.Remove(""+unicode);
			E2U.Remove(entity);
			E2A.Remove(entity);
		}


		#endregion

		#region Private Stuff

		private static string Convert(string input, Dictionary<string, string> dict)
		{
			if (string.IsNullOrEmpty(input)) return input;
			foreach (string key in dict.Keys)
				input = input.Replace(key, dict[key]);
			return input;
		}

        /// <summary></summary>
        /// <param name="namedEntity"></param>
        /// <param name="decimalEntity"></param>
        /// <param name="hexEntity"></param>
        /// <param name="unicode"></param>
        /// <param name="ascii"></param>
		private static void Add (string namedEntity, string decimalEntity,
							string hexEntity, string unicode, string ascii)
		{
			if (namedEntity != null)
			{
				E2A.Add(namedEntity, ascii);
				E2U.Add(namedEntity, unicode);
				U2E.Add(unicode, namedEntity);
			}

			E2A.Add(decimalEntity, ascii);
			E2A.Add(hexEntity, ascii);

			E2U.Add(decimalEntity, unicode);
			E2U.Add(hexEntity, unicode);

			U2A.Add(unicode, ascii);
		}

		//---------------------------------------------------------------------

		/// <summary>
		/// The named HTML entity for the character; e.g. "&amp;" for ampersand.
		/// </summary>
		public readonly string NamedEntity;

		/// <summary>
		/// The decimal HTML entity for the character; e.g. "&#123;".
		/// </summary>
		public readonly string DecimalEntity;

		/// <summary>
		/// The hexadecimal HTML entity for the character; e.g. "&#x89A;".
		/// </summary>
		public readonly string HexEntity;

		/// <summary>
		/// The Unicode character.  A CharEncoding value implicitly casts to this.
		/// </summary>
		public readonly char Unicode;

		/// <summary>
		/// An ASCII approximation for the character.  For character codes 0x20-0x7F,
		/// the translation will be exact.  For other characters, approximations
		/// include removal of diacritics and use of multiple characters.
		/// </summary>
		public readonly string AsciiApproximation;

		/// <summary>
		/// Convert a CharEncoding to its Unicode equivalent.
		/// </summary>
		public static implicit operator char (CharEncoding ce) { return ce.Unicode; }

		/// <summary>
		/// Convert a CharEncoding to its Unicode equivalent.
		/// </summary>
	//	[Obsolete("Do not implicitly cast CharEncoding to a string, use one of its string properties instead.", true)]
		public static implicit operator string (CharEncoding ce) { return ce.Unicode.ToString(); }

		internal CharEncoding (string namedEntity, string decEntity, string hexEntity, string unicode, string asciiApprox)
		{
			this.NamedEntity = namedEntity;
			this.DecimalEntity = decEntity;
			this.HexEntity = hexEntity;
			this.Unicode = unicode[0];
			this.AsciiApproximation = asciiApprox;

			Add(namedEntity, decEntity, hexEntity, unicode, asciiApprox);
		}

		// the conversion dictionaries
		private static readonly Dictionary<string, string> E2A = new Dictionary<string, string>();
		private static readonly Dictionary<string, string> U2A = new Dictionary<string, string>();
		private static readonly Dictionary<string, string> E2U = new Dictionary<string, string>();
		private static readonly Dictionary<string, string> U2E = new Dictionary<string, string>();

		#endregion
	}
}
